package org.taobao.ibatiside.parser;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

public class SqlMapParser{
	public static final int COMMENT = 1;
	public static final int ID=2;
	public static final int QVAR=3; // ?
	public static final int CONST = 20;
	public static final int NVAR = 6; //
	public static final int SVAR = 9;
	public static final int OP=7; // = != <> > >= < <=
	public static final int OTHER=10;
	public static class SqlMapVaraible{
		int start;
		int length;
		int index;
		int type;
		String column="";
		String name = "";
		String literal = "";
		
		public SqlMapVaraible() {
			super();
		}

		public SqlMapVaraible(int start, int length, int index, int type,
				String column, String name, String literal) {
			super();
			this.start = start;
			this.length = length;
			this.index = index;
			this.type = type;
			this.column = column;
			this.name = name;
			this.literal = literal;
		}

		public int getStart() {
			return start;
		}

		public void setStart(int start) {
			this.start = start;
		}

		public int getLength() {
			return length;
		}

		public void setLength(int length) {
			this.length = length;
		}

		public int getIndex() {
			return index;
		}

		public void setIndex(int index) {
			this.index = index;
		}

		public int getType() {
			return type;
		}

		public void setType(int type) {
			this.type = type;
		}

		public String getColumn() {
			return column;
		}

		public void setColumn(String column) {
			this.column = column;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getLiteral() {
			return literal;
		}

		public void setLiteral(String literal) {
			this.literal = literal;
		}

		@Override
		public String toString() {
			StringBuffer buffer = new StringBuffer();
			buffer.append("#").append(index);
			buffer.append(" at [").append(start).append(',').append(length).append("] for ");
			buffer.append((StringUtils.isBlank(column) ? " ?? " : column)).append("->").append(name).append('$').append(literal).append('$');
			buffer.append('(').append(type).append(')');
			return buffer.toString();
		}
	}
	static class Token{
		int start;
		int length;
		int type;
		int index;
		String literal;
		@Override
		public String toString() {
			return literal+"["+type+"] ";
		}
	}
	
	private int offset = 0;
	private List<Token> tokens = new ArrayList<Token>();
	private List<SqlMapVaraible> variables = new ArrayList<SqlMapVaraible>();
	private StringBuffer buffer = new StringBuffer();
	private CharSequence stmt;
	private int seq = 0;
	public List<SqlMapVaraible> getVariables() {
		return variables;
	}
	public void parse(String sql) throws Exception{
		reset();
		stmt = sql+"  ";
		if (StringUtils.isBlank(sql)) return;
		int length = stmt.length();
		do{
			char ch = stmt.charAt(offset);
			if (ch=='$'){
				matchSvar();
			}else if (Character.isJavaIdentifierStart(ch)){
				matchId();
			}else if (Character.isDigit(ch)){
				matchNumber();
			}else if (Character.isSpaceChar(ch)){
				matchSpace();
			}else if (ch == '\''){ //String
				matchString();
			}else if (ch == '/'){
				tryComment();
			}else if (ch == '!'){
				tryOperatorNEQ();
			}else if (ch == '='){
				tryOperatorEQ();
			}else if (ch == '<'){
				tryOperatorNeqLtLte();
			}else if (ch == '>'){
				tryOperatorGtGte();
			}else if (ch == '?'){
				matchQvar();
			}else if (ch == '#'){
				matchNvar();
			}else{
				matchOther();
			}
		}while (offset++ < length);
		syntax();
		semantic();
	}
	private void matchSvar() throws Exception {
		int from = offset;
		offset++;
		while (true){
			abnormal(from);
			char ch = stmt.charAt(offset);
			if (ch == '$'){
				addToken(from, offset, SVAR);
				break;
			}
			buffer.append(ch);
			offset++;
		}
	}
	private void matchOther() {
		//empty loop
	}
	private void semantic() {
		
	}
	private void syntax(){
		reduceVariable();
	}
	private void reduceVariable() {
		for (int i = 0; i < tokens.size(); i++){			
			Token t = tokens.get(i);
			Token p = i > 0 ? tokens.get(i-1) : null;
			Token n = i < tokens.size()-1 ? tokens.get(i+1) : null;
			if (t.type == CONST || t.type == QVAR || t.type == NVAR){
				String column = p != null && p.type == OP && i-2 >=0 && tokens.get(i-2).type == ID ? tokens.get(i-2).literal : "";
				SqlMapVaraible one_variable = new SqlMapVaraible();
				one_variable.column = column;
				one_variable.index = t.index;
				one_variable.start = t.start;
				one_variable.length = t.length;
				one_variable.type = t.type;
				one_variable.literal = (t.type == CONST) ? t.literal : (n != null &&  n.type == COMMENT) ? StringUtils.trim(n.literal) : "";
				one_variable.name = (t.type == NVAR) ? t.literal : "";
				variables.add(one_variable);
			}else if (t.type == SVAR){
				SqlMapVaraible one_variable = new SqlMapVaraible();
				one_variable.index = t.index;
				one_variable.start = t.start;
				one_variable.length = t.length;
				one_variable.type = t.type;
				one_variable.literal = (n != null &&  n.type == COMMENT) ? StringUtils.trim(n.literal) : "";
				one_variable.name = t.literal;
				variables.add(one_variable);
			}
		}
	}
	private void matchNvar() throws Exception {
		int from = offset;
		offset++;
		while (true){
			abnormal(from);
			char ch = stmt.charAt(offset);
			if (ch == '#'){
				addToken(from, offset, NVAR);
				break;
			}
			buffer.append(ch);
			offset++;
		}
	}
	private void matchQvar() {
		buffer.append('?');
		addToken(offset, offset, QVAR);
	}
	private void tryOperatorGtGte() {
		int from = offset;
		buffer.append(stmt.charAt(offset));
		char next_ch = stmt.charAt(++offset);
		if (next_ch != '=' ){
			offset = from;
		}else{
			buffer.append(next_ch);
		}
		addToken(from, offset, OP);
	}
	private void tryOperatorNeqLtLte() {
		int from = offset;
		buffer.append(stmt.charAt(offset));
		char next_ch = stmt.charAt(++offset);
		if (next_ch != '=' && next_ch != '>'){
			offset = from;
		}else{
			buffer.append(next_ch);
		}
		addToken(from, offset, OP);
	}
	private void tryOperatorEQ() { //= Or ==
		int from = offset;
		buffer.append(stmt.charAt(offset));
		char next_ch = stmt.charAt(++offset);
		if (next_ch != '='){
			offset = from;
		}else{
			buffer.append(next_ch);
		}
		addToken(from, offset, OP);
	}
	private void tryOperatorNEQ() {
		int from = offset;
		buffer.append(stmt.charAt(offset));
		char next_ch = stmt.charAt(++offset);
		if (next_ch == '='){
			buffer.append(next_ch);
			addToken(from, offset, OP);
		}else{
			offset = from;
		}
	}
	private void tryComment() throws Exception {
		int from = offset;
		char next_ch = stmt.charAt(++offset);
		if (next_ch == '*'){
			offset++;
			while (true){
				abnormal(from);
				char ch = stmt.charAt(offset);
				if (ch == '*'){
					next_ch = stmt.charAt(offset+1);
					if (next_ch == '/'){
						addToken(from, ++offset, COMMENT);
						break;
					}
				}
				buffer.append(ch);
				offset++;
			}
		}else{
			offset = from; //roll back
		}
	}
	public void printToken(){
		for (Token token : tokens){
			System.out.print(token);
		}
		System.out.println();
	}
	private void matchString() throws Exception {
		int from = offset;
		offset++;
		buffer.append('\'');
		while(true){
			abnormal(from);
			char ch = stmt.charAt(offset);
			if (ch == '\''){
				char pre_ch = stmt.charAt(offset-1);
				if(pre_ch != '\\'){
					buffer.append('\'');
					addToken(from, offset, CONST);
					break;
				}
			}
			buffer.append(ch);
			offset++;
		}
	}
	private void matchSpace() {
		while (offset < stmt.length() && Character.isSpaceChar(stmt.charAt(offset))){
			offset++;
		}
		if (offset != stmt.length()) --offset;
	}
	private void matchNumber() throws Exception {
		int from = offset;
		while(true){
			abnormal(from);
			char ch = stmt.charAt(offset);
			if (Character.isDigit(ch) || ch == '.' ){
				buffer.append(ch);
			}else{
				int end = --offset;
				addToken(from, end, CONST);
				break;
			}
			offset++;
		}
	}
	private void abnormal(int from) throws Exception {
		if(offset == stmt.length()){
			throw new Exception("Can't identify "+stmt+" begin with"+stmt.subSequence(0, from));
		}
	}
	private void matchId() throws Exception {
		int from = offset;
		while(true){
			abnormal(from);
			char ch = stmt.charAt(offset);
			if (Character.isLetterOrDigit(ch) || ch == '_'){
				buffer.append(ch);
			}else{
				addToken(from, --offset, ID);
				break;
			}
			offset++;
		}
	}
	private void addToken(int from, int end, int type) {
		Token t = new Token();
		t.start = from;
		t.length = end-from+1;
		t.type = type;
		t.index = seq++;
		t.literal = buffer.toString();
		buffer.setLength(0);
		tokens.add(t);
	}
	public void reset() {
		tokens.clear();
		buffer.setLength(0);
		variables.clear();
		offset = 0;
		seq = 0;
	}
	
}